﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace DynamicMVVM.SL
{
    public static class DynamicObjectCreator
    {
        private static AssemblyBuilder _assemblyBuilder = null;

        private static ModuleBuilder _moduleBuilder = null;

        private static readonly Type BaseClass = typeof(DynamicModelBase);

        public static string AssemblyName = "DynamicObjectCreator";

        public static DynamicModelBase CreateDynamicModel(object model)
        {
            if (model == null)
            {
                return null;
            }

            var modeType = model.GetType();

            var typeProperties = modeType.GetProperties().ToList();

            var dynamicType = CreateType(modeType, typeProperties);

            var returnObject = Activator.CreateInstance(dynamicType) as DynamicModelBase;

            returnObject.TypeProperties = typeProperties;

            returnObject.SetModel(model);

            return returnObject;
        }

        public static Type CreateType(Type type, List<PropertyInfo> typeProperties)
        {
            GenerateAssemblyAndModule();

            var typeBuilder = CreateType(_moduleBuilder, string.Format("{0}.{1}", AssemblyName, type.FullName));

            foreach (var propertyInfo in typeProperties)
            {
                //CreateProperty(typeBuilder, propertyInfo.Name, propertyInfo.PropertyType);
                CreateWraperProperty(typeBuilder, propertyInfo.Name, propertyInfo.PropertyType, type);
            }

            return typeBuilder.CreateType();
        }


        private static void GenerateAssemblyAndModule()
        {
            if (_assemblyBuilder != null) return;

            var assemblyName = new AssemblyName { Name = AssemblyName };

            var thisDomain = Thread.GetDomain();

            _assemblyBuilder = thisDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

            _moduleBuilder = _assemblyBuilder.DefineDynamicModule(AssemblyName, false);
        }

        private static TypeBuilder CreateType(ModuleBuilder modBuilder, string typeName)
        {
            var typeBuilder = modBuilder
                .DefineType(typeName,
                            TypeAttributes.Public |
                            TypeAttributes.Class |
                            TypeAttributes.AutoClass |
                            TypeAttributes.AnsiClass |
                            TypeAttributes.BeforeFieldInit |
                            TypeAttributes.AutoLayout,
                            typeof(DynamicModelBase));

            //new Type[] { typeof(IMyInterface) });

            return typeBuilder;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            var fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            var propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            var getMethoddBldr = tb.DefineMethod("get_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 propertyType, Type.EmptyTypes);

            var getIL = getMethoddBldr.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

            var setMethoddBldr = tb.DefineMethod("set_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 null, new Type[] { propertyType });

            var setIL = setMethoddBldr.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethoddBldr);
            propertyBuilder.SetSetMethod(setMethoddBldr);
        }

        private static void CreateWraperProperty(TypeBuilder tb, string propertyName, Type propertyType, Type objectType)
        {
            var propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

            var getMethoddBldr = tb.DefineMethod("get_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 propertyType, Type.EmptyTypes);

            var getIL = getMethoddBldr.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            var getModelMethod = BaseClass.GetMethod("get_Model");
            getIL.Emit(OpCodes.Call, getModelMethod);
            var getPropertyMethod = objectType.GetMethod("get_" + propertyName);

            getIL.Emit(OpCodes.Unbox, propertyType);
            getIL.EmitCall(OpCodes.Call, getPropertyMethod,null);

            //getIL.Emit(OpCodes.Ldarg_0);
            //getIL.Emit(OpCodes.Ldstr, propertyName);
            //getIL.Emit(OpCodes.Call, baseClass.GetMethod("GetPropertyValue"));
            getIL.Emit(OpCodes.Ret);

            var setMethoddBldr = tb.DefineMethod("set_" + propertyName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.SpecialName |
                                                 MethodAttributes.HideBySig,
                                                 null, new Type[] { propertyType });

            var setIL = setMethoddBldr.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldstr, propertyName);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Call, BaseClass.GetMethod("SetPropertyValue"));
            setIL.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getMethoddBldr);
            propertyBuilder.SetSetMethod(setMethoddBldr);
        }
    }
}